zhouqijie

一、阴影的几种实现

投影阴影:

  1. 给定一个物体、一个光源、一个平面,生成一个变换矩阵。
  2. 把物体上的点变换为平面上的点,再把阴影多边形绘制出来。

阴影体:

  1. 找到阴影体。
  2. 判断相交部分,进行对应的处理。

阴影贴图

最实用最流行的阴影实现方式。

二、阴影贴图实现

声明相关变量:

int scSizeX, scSizeY;
GLuint shadowTex, shadowBuffer;
glm::mat4 lightVmatrix;
glm::mat4 lightPmatrix;
glm::mat4 shadowMVP1;
glm::mat4 shadowMVP2;
glm::mat4 b;

自定义一个阴影的帧缓冲区:

void init(GLFWwindow * window)
{   
    //...
    //setupVertexBuffers();//顶点缓冲区
	setupShadowBuffers(window);//阴影帧缓冲区
    //...
}

void setupShadowBuffers(GLFWwindow* window)
{
    //定义b矩阵用于将光源空间变换到纹理贴图空间
	b = glm::mat4(
		0.5f, 0.0f, 0.0f, 0.0f,
		0.0f, 0.5f, 0.0f, 0.0f,
		0.0f, 0.0f, 0.5f, 0.0f,
		0.5f, 0.5f, 0.5f, 1.0f);
	glfwGetFramebufferSize(window, &width, &height);
	scSizeX = width;
	scSizeY = height;

	glGenFramebuffers(1, &shadowBuffer);

	glGenTextures(1, &shadowTex);
	glBindTexture(GL_TEXTURE_2D, shadowTex);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32,
		scSizeX, scSizeY, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

	// may reduce shadow border artifacts
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

使用两个Pass来渲染:

void display(GLFWwindow * window, double currentTime)
{
    //...
    glClear(GL_COLOR_BUFFER_BIT);
	glClear(GL_DEPTH_BUFFER_BIT);
    //...

    //光源空间矩阵构建(定向光)
	lightVmatrix = glm::lookAt(cam.e, cam.e + light.dir, up);
	lightPmatrix = glm::ortho(-20.0f, 20.0f, -20.0f, 20.0f, -20.0f, 20.0f);


	//**********  PASS 1  ******************************
	//使用自定义的阴影帧缓冲区,并把阴影纹理附着在上面
	glBindFramebuffer(GL_FRAMEBUFFER, shadowBuffer);
	glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowTex, 0);
	
	glDrawBuffer(GL_NONE);		//关闭绘制颜色
	glEnable(GL_DEPTH_TEST);		//开启深度测试
	glEnable(GL_POLYGON_OFFSET_FILL);	// 开启深度偏移
	glPolygonOffset(2.0f, 4.0f);		//  深度偏移

	passOne();

	glDisable(GL_POLYGON_OFFSET_FILL);	// 关闭深度偏移
	//***************************************************

	//**********  PASS 2  ******************************
	//使用显示的帧缓冲区
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	glActiveTexture(GL_TEXTURE0);		//纹理单元0-绑定阴影纹理
	glBindTexture(GL_TEXTURE_2D, shadowTex);	
	glActiveTexture(GL_TEXTURE1);		//纹理单元1-绑定物体纹理
	glBindTexture(GL_TEXTURE_2D, myTexture);

	glDrawBuffer(GL_FRONT);//重新开启绘制颜色

	passTwo();
	//***************************************************
}

其中两个Pass的定义:

//pass 1
void passOne(void)
{
	glUseProgram(renderingProgram1);

	// 绘制当前模型
	mMat = glm::translate(glm::mat4(1.0f), modelPos);
	mMat = glm::rotate(mMat, toRadians(25.0f), glm::vec3(1.0f, 0.0f, 0.0f));

	shadowMVP1 = lightPmatrix * lightVmatrix;
	sLoc = glGetUniformLocation(renderingProgram1, "shadowMVP");
	glUniformMatrix4fv(sLoc, 1, GL_FALSE, glm::value_ptr(shadowMVP1));

	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);

	glClear(GL_DEPTH_BUFFER_BIT);
	glEnable(GL_CULL_FACE);
	glFrontFace(GL_CCW);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);

	glDrawArrays(GL_TRIANGLES, 0, myModel.getNumVertices());

	// 绘制其他模型....
}

//pass 2
void passTwo(void)
{
	glUseProgram(renderingProgram2);

	// 绘制当前模型
	vMat = cam.GetMatrixV();
	mMat = glm::translate(glm::mat4(1.0f), modelPos);
	mvMat = vMat * mMat;
	invTrMat = transpose(inverse(mvMat));//逆转置矩阵
	shadowMVP2 = b * lightPmatrix * lightVmatrix;//b矩阵用于将光源空间变换到纹理贴图空间

	mLoc = glGetUniformLocation(renderingProgram2, "m_matrix");
	vLoc = glGetUniformLocation(renderingProgram2, "v_matrix");
	mvLoc = glGetUniformLocation(renderingProgram2, "mv_matrix");
	projLoc = glGetUniformLocation(renderingProgram2, "proj_matrix");
	nLoc = glGetUniformLocation(renderingProgram2, "norm_matrix");
	sLoc = glGetUniformLocation(renderingProgram2, "shadowMVP");


	ambLoc = glGetUniformLocation(renderingProgram2, "ambient");
	dirLightDirLoc = glGetUniformLocation(renderingProgram2, "light.dir");
	dirLightColorLoc = glGetUniformLocation(renderingProgram2, "light.color");
	mDiffLoc = glGetUniformLocation(renderingProgram2, "material.diffuse");
	mSpecLoc = glGetUniformLocation(renderingProgram2, "material.specular");
	mGlosLoc = glGetUniformLocation(renderingProgram2, "material.gloss");


	glUniformMatrix4fv(mLoc, 1, GL_FALSE, glm::value_ptr(mMat));
	glUniformMatrix4fv(vLoc, 1, GL_FALSE, glm::value_ptr(vMat));
	glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
	glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
	glUniformMatrix4fv(nLoc, 1, GL_FALSE, glm::value_ptr(invTrMat));
	glUniformMatrix4fv(sLoc, 1, GL_FALSE, glm::value_ptr(shadowMVP2));
	glUniform4fv(ambLoc, 1, glm::value_ptr(ambientColor));
	glUniform4fv(dirLightColorLoc, 1, glm::value_ptr(light.color));
	glUniform3fv(dirLightDirLoc, 1, glm::value_ptr(light.dir));
	glUniform4fv(mDiffLoc, 1, glm::value_ptr(materialDiff));
	glUniform4fv(mSpecLoc, 1, glm::value_ptr(materialSpec));
	glUniform1fv(mGlosLoc, 1, &materialGloss);
    
	//传递顶点属性-顶点位置
	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);

	//传递顶点属性-顶点纹理坐标
	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(1);

	//传递顶点属性-顶点法线
	glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
	glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(2);

	
	//设置
	glEnable(GL_CULL_FACE);
	glFrontFace(GL_CCW);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);

	//绘制图形
	glDrawArrays(GL_TRIANGLES, 0, myModel.getNumVertices());

	// 绘制其他模型....
}

渲染阴影纹理(Pass1)使用的着色器代码:

//顶点着色器(仅变换到光源空间)
#version 430
layout (location=0) in vec3 vertPos;
uniform mat4 shadowMVP;

void main(void)
{	gl_Position = shadowMVP * vec4(vertPos,1.0);
}
//片段着色器(不执行任何操作,仅深度写入)
#version 430
void main(void) {}

第二轮渲染(Pass2)的着色器中加上以下代码:

//顶点着色器
//...
uniform mat4 shadowMVP;
layout (binding = 0) uniform sampler2DShadow shadowTex;
out vec4 shadow_coord;
//...
void main(void)
{
    //...
    shadow_coord = shadowMVP * vec4(position,1.0);
    //...
}
//片段着色器
//...
uniform mat4 shadowMVP;
layout(binding = 0) uniform sampler2DShadow shadowTex;
in vec4 shadow_coord;
//...
void main(void)
{
    //...
    float inShadow = textureProj(shadowTex, shadow_coord);
    if (inShadow != 0.0)
    {
    	color = vec4((ambient.xyz + diffuse + specular), 1.0); 
    }
    else
    {
    	color = vec4((ambient.xyz), 1.0); 
    }
}

关于阴影痤疮和偏移(Bias)设置:

可以使用glPolygonOffset(value, value);来设置偏移量来避免阴影痤疮。
需要开启深度偏移。(glEnable(GL_POLYGON_OFFSET_FILL);)
渲染完成后关闭。(glDisable(GL_POLYGON_OFFSET_FILL);)


关于柔和阴影:

使用百分比临近滤波(PCF)。

顶点着色器修改:

float sampShadowTex(float ox, float oy)
{
	//把(1 / windowsize)的值硬编码为0.001。
	//第三个参数也可以避免阴影痤疮。(和glPolygonOffset()作用一样)
	 return textureProj(shadowTex, shadow_coord + vec4(ox * 0.001 * shadow_coord.w, oy * 0.001 * shadow_coord.w, -0.005, 0.0));
}

//......
float shadowFactor = 0.0;
float swidth = 1.0;
vec2 offset = mod(floor(gl_FragCoord.xy), 2.0);//抖动值。有四个值(0,0)(0,1)(1,0)(1,1)
shadowFactor += sampShadowTex((-1.5 + offset.x) * swidth, (1.5 - offset.y) * swidth);
shadowFactor += sampShadowTex((-1.5 + offset.x) * swidth, (-0.5 - offset.y) * swidth);
shadowFactor += sampShadowTex((0.5 + offset.x) * swidth, (1.5 - offset.y) * swidth);
shadowFactor += sampShadowTex((0.5 + offset.x) * swidth, (-0.5 - offset.y) * swidth);
shadowFactor = shadowFactor / 4.0;

//......
color = vec4(ambient + shadowFactor * (diffuse + specular), 1.0);